package com.gitee.wsl.net.server

import com.gitee.wsl.ext.string.toByteArray
import io.ktor.http.ContentType
import io.ktor.http.HttpMethod
import io.ktor.serialization.kotlinx.json.json

import io.ktor.server.application.Application
import io.ktor.server.application.call
import io.ktor.server.application.install
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
import io.ktor.server.plugins.cors.routing.CORS
import io.ktor.server.plugins.statuspages.StatusPages
import io.ktor.server.plugins.statuspages.StatusPagesConfig
import io.ktor.server.resources.Resources
import io.ktor.server.response.respond
import io.ktor.server.response.respondRedirect
import io.ktor.server.response.respondText
import io.ktor.server.routing.get
import io.ktor.server.routing.routing
import io.ktor.server.sessions.CurrentSession
import io.ktor.server.sessions.SessionTransportTransformerMessageAuthentication
import io.ktor.server.sessions.Sessions
import io.ktor.server.sessions.cookie
import io.ktor.server.sessions.get
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlin.reflect.KClass

object KtorServer {

    private val server by lazy {
        embeddedServer(Netty, 12345) {

            // Install the Sessions plugin with [MySession] class stored in the "MYSESSION_ID" cookie authenticated
            // to prevent manipulation, unless the [secretHashKey] has been leaked.
            install(Sessions) {
                cookie<MySession>("MYSESSION_ID") {
                    transform(SessionTransportTransformerMessageAuthentication("6819b57a326945c1968f45236581".toByteArray()))
                }
            }

            // Allows using classes annotated with @Resource to represent URLs.
            // They are typed, can be constructed to generate URLs, and can be used to register routes.
            install(Resources)

            // Automatic '304 Not Modified' Responses
            //install(ConditionalHeaders)
            // Supports for Range, Accept-Range and Content-Range headers
            //install(PartialContent)
            //install(CallLogging)
            // 跨域访问
            install(CORS) {
                anyHost()
                listOf(HttpMethod.Put, HttpMethod.Delete).forEach {
                    allowMethod(it)
                }
                /*header(HttpHeaders.ContentType)
                method(HttpMethod.Options)
                method(HttpMethod.Put)
                method(HttpMethod.Patch)
                method(HttpMethod.Delete)*/
            }

            // The StatusPages plugin allows catching unhandled exceptions.
            // We are going to use it to catch redirection exceptions and actually perform redirects
            // We are also going to use it to catch exceptions of sessions not found to redirect to our desired pages.
            install(StatusPages) {
                registerRedirections()
                registerSessionNotFoundRedirect<MySession>("/login")
            }

            install(ContentNegotiation) {
                json()
            }

            handleException()

            routing {
                get("/") {
                    call.respondText("手机型号 运行正常", ContentType.Text.Plain)
                }
            }
        }
    }

    /** 启动服务器 */
    fun start() {
        CoroutineScope(Dispatchers.IO).launch { server.start(true) }
    }

    /** 停止服务器 */
    fun stop() {
        server.stop(1_000, 2_000)
    }
}

val handleException: Application.() -> Unit = {
    install(StatusPages) {
        exception<CustomExceptions>{call,it->
            call.respond(ResponseBase(it.status, null, it.description))
        }
        exception<Throwable> {call,it->
            it.printStackTrace()
            call.respond(ResponseBase(9999, null, "Unknown error"))
        }
    }
}


open class CustomExceptions(val status: Int, val description: String) : Exception(description)

class MissingParamsException(param: String) : CustomExceptions(100, "Missing parameter: $param")
class GeneralException(description: String) : CustomExceptions(999, description)

data class ResponseBase<T>(
    val status: Int = 0,
    val data: T? = null,
    val message: String = "Success"
)

/**
 * Exception used to be captured by [StatusPages] (or caught) to notify that the session couldn't be found,
 * so the application can do things like redirect. It stores the session that couldn't be retrieved to be able
 * to have different behaviours.
 */
class SessionNotFoundException(val clazz: KClass<*>) : Exception()

/**
 * Convenience method to try to get an exception of type [T], or to throw a [SessionNotFoundException] to
 * handle it either by catching or by using the [StatusPages] plugin.
 */
inline fun <reified T> CurrentSession.getOrThrow(): T =
    this.get() ?: throw SessionNotFoundException(T::class)


/**
 * Typed session that will be used in this application.
 */
data class MySession(val user: String)


/**
 * Exception used to be captured by [StatusPages] to perform a redirect.
 */
class RedirectException(val path: String, val permanent: Boolean) : Exception()

/**
 * Global function that throws a [RedirectException], to be catched by the [StatusPages] plugin to perform a redirect
 * to [path].
 */
fun redirect(path: String, permanent: Boolean = false): Nothing = throw RedirectException(path, permanent)


/**
 * Extension method for configuring [StatusPages] that encapsulates the functionality of catching
 * the [RedirectException] and actually performing a redirection.
 */
fun StatusPagesConfig.registerRedirections() {
    exception<RedirectException> { call, cause ->
        call.respondRedirect(cause.path, cause.permanent)
    }
}

/**
 * Extension method for configuring [StatusPages] that encapsulates the functionality of catching
 * the [SessionNotFoundException] to redirect to the [path] page in the case of the session [T].
 */
inline fun <reified T> StatusPagesConfig.registerSessionNotFoundRedirect(path: String) {
    exception<SessionNotFoundException> { call, cause ->
        if (cause.clazz == T::class) call.respondRedirect(path)
    }
}